package vn.edu.saigontech;

import ch.aplu.jgamegrid.Actor;
import ch.aplu.jgamegrid.GGBitmap;
import ch.aplu.jgamegrid.GGMouse;
import ch.aplu.jgamegrid.GGMouseListener;
import ch.aplu.jgamegrid.GGMouseTouchListener;
import ch.aplu.jgamegrid.GGPanel;
import ch.aplu.jgamegrid.GameGrid;
import ch.aplu.jgamegrid.Location;
import ch.aplu.jgamegrid.TextActor;
import ch.aplu.nxtsim.NxtRobot;
import ch.aplu.nxtsim.Obstacle;
import ch.aplu.util.Cleanable;
import ch.aplu.util.Monitor;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSlider;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 * 
 * @author Saigon Institute of Technology
 *
 */
public class SaigonTechNxtStudio extends JFrame implements ActionListener, GGMouseListener, GGMouseTouchListener,  WindowListener, Cleanable {	
	
	private static final Color MENU_SELECTION_BACKGROUND = new Color(224, 138, 56);
	
	static {
		//SaigonTech customizations
		SaigonTechNxtContext.setIsNavigationBar(Boolean.TRUE);
		SaigonTechNxtContext.setStartLocation(new Location(388, 200));
		SaigonTechNxtContext.setTitle("SaigonTech NXT Kit (Beta4 version)");			
		
		//Look & Feel Settings
		UIManager.put("Menu.selectionBackground", MENU_SELECTION_BACKGROUND);
		UIManager.put("Menu.selectionForeground", Color.white);
		UIManager.put("MenuItem.selectionBackground", MENU_SELECTION_BACKGROUND);
		UIManager.put("MenuItem.selectionForeground", Color.white);
	}
	
	private static final int STUDIO_WIDTH = 1000;
	private static final int STUDIO_HEIGHT = 750;
	
	private int studioWidth = STUDIO_WIDTH;
	private int studioHeight = STUDIO_HEIGHT;
	
	//Menu bar
	private JMenuBar menuBar = new MyMenuBar("sprites/barbg.png");
	
	//Home menu: come back action
	private JMenu homeMenu = new TransparentMenu("Giao diện chính");
	//Home menu: come back action
	private JMenuItem comebackMenuItem = new JMenuItem("Quay về");
	
	//File menu: save/load action
	private JMenu fileMenu = new TransparentMenu("Tập tin");
	//Save menu item: terrain, configuration
	private JMenuItem saveMenuItem = new JMenuItem("Lưu xuống");
	//Load menu item: terrain, configuration
	private JMenuItem loadMenuItem = new JMenuItem("Mở ra");
	
	//List of supported robot configurations
	private JMenu supportedRobots = new TransparentMenu("Danh sách robot");	
	
	//Tools set menu: design, configuration
	private JMenu toolMenu = new TransparentMenu("Chức năng hoạt động");
	//General Robot configuration menu item
	private JMenuItem robotConfigMenuItem = new JMenuItem("Cấu hình Robot");
	//Car Robot configuration menu item
	private JMenuItem carConfigMenuItem = new JMenuItem("Cấu hình Car robot");
	//Simulation mode menu item
	private JMenuItem simulationModeItem = new JMenuItem("Chế độ Giả lập"); 
	
	//Simulation container
	private JPanel simPanel = new JPanel();
	
	//Controller container
	private JPanel ctrPanel = new JPanel();
	
	//Extensions container
	private JPanel smartPanel = new JPanel();
	
	//Robot LCD display	
	private JTextArea lcdSim  = new JTextArea(3, 71);
	
	//Mouse tooltips
	//Pixel location
	private TextActor toolTip1 = new TextActor("");
	//Ruler location
	private TextActor toolTip2 = new TextActor("");
	
	//Tools box
	private JToolBar toolsBox = new JToolBar(JToolBar.VERTICAL);	
	
	//Simulation board
	private GameGrid table = null;
	
	//Layer designer
	private GGPanel panel = null;
	
	//Snapshot of current painting
	private BufferedImage snapshot = null;
	
	//List of obstacles on the board
	private List<Wall> walls = new ArrayList<Wall>();
	
	//Save selected location
	private Point hotSpot = null;
	
	//Secondary hotspot: retrieve last hotspot that deleted before
	private Point slaveHotSpot = null;
	
	//Save selected actor
	private Actor selection = null;
	
	//Layer of background
	private BufferedImage bgLayer = null;
	
	//Toggle button trackers
	//Control
	private boolean isPlaying = Boolean.FALSE;	
	//Tools
	private boolean isWall = Boolean.FALSE;
	private boolean isRotate = Boolean.FALSE;
	private boolean isDelete = Boolean.FALSE;
	private boolean isPaint = Boolean.FALSE;
	private boolean isEraser = Boolean.FALSE;	
	
	//Buttons
	//Control simulation 
	private JToggleButton playBtn = new MyToggleButton("sprites/buttonbg.png");
	
	//Tools
	//Create walls
	private JToggleButton wallBtn = new MyToggleButton("sprites/buttonbg.png");
	//Rotate walls on board
	private JToggleButton rotateBtn = new MyToggleButton("sprites/buttonbg.png");
	//Remove walls on board	
	private JToggleButton deleteBtn = new MyToggleButton("sprites/buttonbg.png");
	//Paint lines on board
	private JToggleButton paintBtn = new MyToggleButton("sprites/buttonbg.png");
	//Erase paint lines on board
	private JToggleButton eraserBtn = new MyToggleButton("sprites/buttonbg.png");
	//Upload a painting to board instead of painting directly
	private JButton uploadBtn = new MyButton("sprites/buttonbg.png");
	//Mode switcher
	private JToggleButton switchBtn = new MyToggleButton("sprites/buttonbg.png");
	
	//Width slider 
	//Min: 6px, max: 48px, default: 12px
	private JSlider widthSlider = new JSlider(JSlider.HORIZONTAL, 6, 72, 12);
	
	//Color picker	
	private Color currentColor = null;
	private List<Color> wallColors = new ArrayList<>();
	
	public SaigonTechNxtStudio() {
		
		//Initialize menu bar
		initMenuBar();		
		
		//Show tools
		initToolsBox();	
		
		//Set logo icon
		setIconImage(GGBitmap.getImage("sprites/studioicon.png"));
		
		//Set title for window application
		setTitle(SaigonTechNxtContext.getTitle());
		
		//Init Nimbus look & feel
//		try {
//		    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
//		        if ("Nimbus".equals(info.getName())) {
//		            UIManager.setLookAndFeel(info.getClassName());
//		            break;
//		        }
//		    }
//		} catch (Exception e) {
//		    // If Nimbus is not available, you can set the GUI to another look and feel.
//		}
		
		SwingUtilities.invokeLater(new SetupTableSafety(this));
		
		//Preferred size
		setPreferredSize(new Dimension(studioWidth, studioHeight));
		
		//Lock window size
		setResizable(Boolean.FALSE);
		
		//Show window
		setVisible(Boolean.TRUE);
	}		
	
	//Initialize menu system
	private void initMenuBar() {
		Font font = new Font(Font.SERIF, Font.BOLD, 15);
		
		this.menuBar.setPreferredSize(new Dimension(955, 35));	
		this.menuBar.setLayout(new FlowLayout(FlowLayout.LEADING, 5, 10));		
		
		//File menu
		this.fileMenu.add(this.saveMenuItem);
		this.fileMenu.add(this.loadMenuItem);	
		this.fileMenu.setForeground(Color.white);
		this.fileMenu.setFont(font);
		
		this.saveMenuItem.setForeground(Color.white);
		this.loadMenuItem.setForeground(Color.white);
		
		this.fileMenu.getPopupMenu().setPopupSize(200, 25 * 2);	
		this.menuBar.add(this.fileMenu);		
		
		this.saveMenuItem.addActionListener(new SaveListener());
		
		//Supported Robot Configurations
		loadSupportRobotConfigurations();	
		this.supportedRobots.setForeground(Color.white);
		this.supportedRobots.setFont(font);			
		
		this.supportedRobots.getPopupMenu().setPopupSize(200, 25 * RobotConfigurationDAO.getSupportedRobot().length);	
		this.menuBar.add(supportedRobots);
		
		//Tools menu
		this.toolMenu.add(this.robotConfigMenuItem);
		this.toolMenu.add(this.carConfigMenuItem);
		this.toolMenu.add(this.simulationModeItem);
		this.toolMenu.setForeground(Color.white);
		this.toolMenu.setFont(font);		
		
		this.robotConfigMenuItem.setForeground(Color.white);
		this.carConfigMenuItem.setForeground(Color.white);
		this.simulationModeItem.setForeground(Color.white);
		
		this.toolMenu.getPopupMenu().setPopupSize(200, 25 * 3);	
		this.menuBar.add(this.toolMenu);		
		
		this.robotConfigMenuItem.addActionListener(new RobotConfigurationActionListener());
		this.carConfigMenuItem.addActionListener(new CarConfigurationActionListener());
		this.simulationModeItem.addActionListener(new SimulationModeActionListener());		
		
		//Home menu
			
		this.homeMenu.add(this.comebackMenuItem);
		this.comebackMenuItem.setForeground(Color.white);
		this.homeMenu.setForeground(Color.white);
		this.homeMenu.setFont(font);
		this.homeMenu.getPopupMenu().setPopupSize(200, 25 * 1);	

		this.menuBar.add(Box.createHorizontalStrut(345));
		this.menuBar.add(this.homeMenu);
		
		this.comebackMenuItem.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				simulationModeItem.doClick();
			}
		});
		
		//Add bar
		this.menuBar.setBorderPainted(Boolean.FALSE);
//		this.setJMenuBar(this.menuBar);
	}


	private void initComponents() {
		//Safe closing
		setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		addWindowListener(this);
		
		//Switch to MAIN mode
		setupMainMode();
		
		//Total layout built
		setupGlobalLayout();		
		
		//Setup LCD Sim
		lcdSim.setEditable(Boolean.FALSE);
//		lcdSim.setBorder(BorderFactory.createBevelBorder(1));

		//Totally organize    
		pack();
	}

	/**
	 * Main mode setup
	 */
	private void setupMainMode() {
		//Add tools box to controller panel with border title
		this.ctrPanel.setOpaque(Boolean.FALSE);
		this.ctrPanel.setLayout(null);
//		this.toolsBox.setPreferredSize(new Dimension(70, 600));
		
		setupPanel(this.ctrPanel, this.toolsBox, "Công cụ");

		//Add terrain to simulation panel with border title
		setupPanel(this.simPanel, this.table, "Bản đồ Địa hình");
		
		//Add default smart panel
		setupPanel(this.smartPanel, this.lcdSim, "Mở rộng");
		
		this.lcdSim.setFont(new Font(Font.SERIF, Font.PLAIN, 15));
		this.lcdSim.setText("Saigon Institute of Technology - Quang Trung Tower, Lot 14, Road 5, Quang Trung Software City, Dist.12, HCMC, Vietnam");
	}

    /**
     * Initialize total layout
     */
	private void setupGlobalLayout() {
		setContentPane(new BackgroundPanel("sprites/wallpaper.png"));
		getContentPane().setLayout(null);

		menuBar.setBounds(80, 120, 1000, 35);
		
		simPanel.setBounds(40, 175, 775, 400);
		ctrPanel.setBounds(825, 175, 130, 400);
		smartPanel.setBounds(40, 585, 775, 50);
		
//		horizontalRuler.setBounds(105, 58, 625, 40);
//		verticalRuler.setBounds(68, 95, 40, 400);
		
		horizontalRuler.setBounds(40, 150, 775, 40);
		verticalRuler.setBounds(15, 175, 40, 400);

		getContentPane().add(horizontalRuler);
		getContentPane().add(verticalRuler);
		
		getContentPane().add(simPanel);
		getContentPane().add(ctrPanel);
		getContentPane().add(smartPanel);
		
		getContentPane().add(menuBar);
	}   

    /**
     * Setup layout & content for a panel
     * @param panel panel that need setup
     * @param content content included
     * @param title border title
     */
	private void setupPanel(JPanel panel, Component content, String title) {
		
		//Clean & clear
		panel.removeAll();
		
		//Panel border
//		panel.setBorder(BorderFactory.createTitledBorder(title));		
		
		//Panel layout initialize
		GroupLayout panelLayout = new GroupLayout(panel);
		panel.setLayout(panelLayout);		
        
		//Horizontal layout 
		panelLayout.setHorizontalGroup(panelLayout.createSequentialGroup().addComponent(
								content, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE));
		
		//Vertical layout
		panelLayout.setVerticalGroup(panelLayout.createSequentialGroup().addComponent(
								content, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE));
	}   

	private void initToolsBox() {
		//Bounds using in Null layout only
		int[] xpos = {0, 0, 0, 0, 0, 0, 0, 0};
		int[] ypos = {0, 40, 80, 120, 160, 200, 240, 280};
		
		//Fix position
		toolsBox.setFloatable(Boolean.FALSE);
		//Make transparency
		toolsBox.setOpaque(false);		
		//Background settings
//		toolsBox.setBackground(Color.white);
		
		//Initiate, bind listener and add simulation control button to tools box
		initToggleButton(playBtn, 0, 0, 
				"sprites/play_0.png", "sprites/play_1.png", "sprites/play_2.png", "sprites/play_3.png");
		toolsBox.add(playBtn);		
		playBtn.addActionListener(this);
		
		//Initiate, bind listener and add wall creating button to tools box
		initToggleButton(wallBtn, 0, 0, 
				"sprites/wall_0.png", "sprites/wall_1.png", "sprites/wall_2.png", "sprites/wall_1.png");
		toolsBox.add(wallBtn);		
		wallBtn.addActionListener(this);
		
		//Initiate, bind listener and add wall rotating button to tools box
		initToggleButton(rotateBtn, 0, 0, 
				"sprites/rotate_0.png", "sprites/rotate_1.png", "sprites/rotate_2.png", "sprites/rotate_1.png");
		toolsBox.add(rotateBtn);
		rotateBtn.addActionListener(this);
		
		//Initiate, bind listener and add wall deleting button to tools box
		initToggleButton(deleteBtn, 0, 0, 
				"sprites/delete_0.png", "sprites/delete_1.png", "sprites/delete_2.png", "sprites/delete_1.png");
		toolsBox.add(deleteBtn);
		deleteBtn.addActionListener(this);
		
		//Initiate, bind listener and add line painting button to tools box
		initToggleButton(paintBtn, 0, 0, 
				"sprites/paint_0.png", "sprites/paint_1.png", "sprites/paint_2.png", "sprites/paint_1.png");
		toolsBox.add(paintBtn);
		paintBtn.addActionListener(this);
		
		//Initiate, bind listener and add paint erasing button to tools box
		initToggleButton(eraserBtn, 0, 0, 
				"sprites/eraser_0.png", "sprites/eraser_1.png", "sprites/eraser_2.png", "sprites/eraser_1.png");
		toolsBox.add(eraserBtn);
		eraserBtn.addActionListener(this);
		
		//Initiate, bind listener and add painting upload button to tools box
		initButton(uploadBtn, 0, 0, 
				"sprites/upload_0.png", "sprites/upload_1.png", "sprites/upload_2.png");
		toolsBox.add(uploadBtn);
		uploadBtn.addActionListener(new UploadFileListener(this));
		
		//Initiate, bind listener and add painting upload button to tools box
		initToggleButton(switchBtn, 0, 0, 
				"sprites/nxt_0.png", "sprites/nxt_1.png", "sprites/nxt_2.png", "sprites/nxt_3.png");
		toolsBox.add(switchBtn);		
		switchBtn.addActionListener(this);
		
		//Simulation mode is default
		switchBtn.setSelected(Boolean.TRUE);
	  }
	
	/**
	 * Decorate a button preparing for transparency, mouse interaction 
	 * @param button origin button
	 * @param x x-pos for null layout
	 * @param y y-pos for null layout
	 * @param sprites states for default, press and hover 
	 */
	private void initButton(JButton button, int x, int y, String... sprites) {
		//Load icons
		Icon defaultIcon = new ImageIcon(GGBitmap.getImage(sprites[0]));
		Icon pressIcon = new ImageIcon(GGBitmap.getImage(sprites[1]));
		Icon hoverDefaultIcon = new ImageIcon(GGBitmap.getImage(sprites[2]));
		Icon disabledIcon = defaultIcon;
		
		//Setup states of button
		button.setIcon(defaultIcon);
		button.setPressedIcon(pressIcon);
		button.setRolloverIcon(hoverDefaultIcon);
		button.setRolloverEnabled(Boolean.TRUE);
		
		//Disabled state
		button.setDisabledIcon(disabledIcon);
		
		//Make transparency
		button.setBorderPainted(Boolean.FALSE);
		button.setContentAreaFilled(Boolean.FALSE);
		button.setFocusPainted(Boolean.FALSE);
		button.setOpaque(Boolean.FALSE);
		
		//Initialize coordinates
		button.setBounds(x, y, defaultIcon.getIconWidth(), defaultIcon.getIconHeight());
		
		//Clean margin
		button.setMargin(new Insets(0, 0, 0, 0));
	}

	/**
	 * Decorate a toggle button preparing for transparency, mouse interaction
	 * @param toggleButton original button
	 * @param x x-pos for null layout
	 * @param y y-pos for null layout
	 * @param sprites states for default, selected, mouse hover
	 */
	private void initToggleButton(JToggleButton toggleButton, int x, int y, String... sprites) {
		//Load icons
		Icon defaultIcon = new ImageIcon(GGBitmap.getImage(sprites[0]));
		Icon selectedIcon = new ImageIcon(GGBitmap.getImage(sprites[1]));
		Icon hoverDefaultIcon = new ImageIcon(GGBitmap.getImage(sprites[2]));
		Icon hoverSelectedIcon = new ImageIcon(GGBitmap.getImage(sprites[3]));
		Icon disabledIcon = defaultIcon;
		
		//Setup states of button
		toggleButton.setIcon(defaultIcon);
		toggleButton.setSelectedIcon(selectedIcon);
		toggleButton.setRolloverIcon(hoverDefaultIcon);
		toggleButton.setRolloverSelectedIcon(hoverSelectedIcon);		
		toggleButton.setRolloverEnabled(Boolean.TRUE);
		
		//Disabled state
		toggleButton.setDisabledIcon(disabledIcon);
		toggleButton.setDisabledSelectedIcon(disabledIcon);
		
		//Make transparency
		toggleButton.setBorderPainted(Boolean.FALSE);
		toggleButton.setContentAreaFilled(Boolean.FALSE);
		toggleButton.setFocusPainted(Boolean.FALSE);
		toggleButton.setOpaque(Boolean.FALSE);
		
		//Initialize coordinates
		toggleButton.setBounds(x, y, defaultIcon.getIconWidth(), defaultIcon.getIconHeight());
		
		//Clean margin
		toggleButton.setMargin(new Insets(0, 0, 0, 0));
	}
	
	/**
	 * Create new robot object returning to simulation
	 * @return NXT robot
	 */
	public NxtRobot newNxtRobot() {
		//Hold the simulation still ready
		if (!isPlaying) Monitor.putSleep();
		
		return new NxtRobot(this.table);
	}

	@Override
	public void clean() {
		// TODO by Binh Nguyen
	}	
	
	/*
	 * (non-Javadoc)
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	@Override
	public void actionPerformed(ActionEvent event) {
		JToggleButton toggleButton = (JToggleButton) event.getSource();
		boolean toggled = toggleButton.isSelected();
		
		//Reset un-push status and trackers to all toggle buttons except playing button
		resetToolsBox();
		
		//Reset Smart panel
		initSmartTools(Boolean.FALSE);
		
		//handle play button toggling
		if (toggleButton == playBtn) {
			//Turn on tracker
			isPlaying = toggled;
			//Synchronize disable or enable other buttons (both simple and toggle) excepts play button
			enableToolsBox(!toggled);
			if (toggled) {	
				//Configure walls on the simulation board
				for (Wall each : walls) {
					SaigonTechNxtContext.getObstacles().add(each);
					SaigonTechNxtContext.getObstacleLocations().add(each.getLocation());
					//Remove walls created in editor (will be re-built by simulator automatically)
					each.removeSelf(); 
				}
				//clean collection of walls
				walls.clear();				
				
				//Background processing
				//Create temporary background from snapshot 
				if (bgLayer != null) {//Background drawn already
				File tempBg;
				try {tempBg = File.createTempFile("background", ".png");} catch (Exception e) { tempBg = new File("bg.png");}
				//Write snapshot to temporary file
				GGBitmap.writeImage(bgLayer, tempBg.getAbsolutePath(), "png");
				//Set background
				table.setBgImagePath(tempBg.getAbsolutePath());
				
				//Remove the snapshot
				snapshot = null;	
				//Clean layer
				bgLayer = null;
				
				//Clean current painting
				panel.clean();
				//End of Background processing
				}
				//Resume the simulation 
				Monitor.wakeUp();
			}
			
			//Reverse design editor isn't allowed in this version
//			toggleButton.setEnabled(Boolean.FALSE);			
		}
		
		//handle wall button toggling		
		if (toggleButton == wallBtn) {
			isWall = toggled;
			
			//Create draw-wall on board: you must UN-push toggle by yourself
			if (!toggled && snapshot != null) {				
				//Build wall from painting
				Wall wall = new Wall(snapshot);

				// Add Touch listener to every wall on the board
				wall.addMouseTouchListener(this, GGMouse.lPress	| GGMouse.lRelease, Boolean.TRUE);
				
				// Store wall to collection
				walls.add(wall);

				// Add new wall to board at default location
				table.addActor(wall, SaigonTechNxtContext.getStartLocation());	
				
				//Remove the snapshot
				snapshot = null;	
				
				//Clean current painting
				panel.clean();
				
				//Restore previous background
				panel.drawImage(bgLayer);
				
				//Refresh painting
				table.refresh();
												
				//Clean last location
				hotSpot = null;
				
				//Clean slave
				slaveHotSpot = null;
				
				//Clear temp colors
				wallColors.clear();
			}
			
			//Width & color support
			initSmartTools(isWall);			
		}
		
		//handle rotate button toggling		
		if (toggleButton == rotateBtn) isRotate = toggled;
		
		//handle delete button toggling
		if (toggleButton == deleteBtn) isDelete = toggled;	
		
		//handle paint button toggling		
		if (toggleButton == paintBtn) {
			isPaint = toggled;
			
			//Draw background on board: You must un-push toggle button by yourself
			if (!toggled && bgLayer != null) {				
				
				//Draw snapshot				
//				table.getBg().drawImage(bgLayer);			
				
				
				//Remove the snapshot
//				snapshot = null;	
				
				//Clean current painting
//				panel.clean();
				
				//Clean last location
				hotSpot = null;
				
				//Clean slave
				slaveHotSpot = null;
			}
			
			//Width & color support
			initSmartTools(isPaint);
		}
		
		//handle eraser button toggling
		if (toggleButton == eraserBtn) {
			isEraser = toggled;	
			
			//Draw background on board: You must un-push toggle button by yourself
			if (!toggled && bgLayer != null) {				
				
				//Draw snapshot
//				table.getBg().drawImage(bgLayer);			
				
				//Remove the snapshot
//				snapshot = null;	
				
				//Clean current painting
//				panel.clean();
				
				//Clean last location
				hotSpot = null;
				
				//Clean slave
				slaveHotSpot = null;
			}
			
			//Width & color support
			initSmartTools(isEraser);
		}
		
		//Update status of the button
		toggleButton.setSelected(toggled);		
	}
	
	/**
	 * Init smart tools in smart panel
	 * @param initOrClean
	 */
	private void initSmartTools(boolean initOrClean) {
		smartPanel.removeAll();//Clean & clear	
		smartPanel.setVisible(initOrClean);
		
		if (initOrClean) {		
			smartPanel.setLayout(new FlowLayout());		
			//Turn on labels at major tick marks.
			widthSlider.setMajorTickSpacing(6);
			widthSlider.setMinorTickSpacing(1);
			widthSlider.setPaintTicks(true);
			widthSlider.setPaintLabels(true);		
			
			//Add slider
			smartPanel.add(new JLabel("Độ lớn:"));
			smartPanel.add(widthSlider);
			
			currentColor = isWall? Color.orange: isPaint? Color.darkGray: Color.white;
			
			if (isWall || isPaint) {
				JButton colorButton = new JButton("Màu sắc");
				smartPanel.add(colorButton);
				
				colorButton.addActionListener(new ActionListener() {				
					@Override
					public void actionPerformed(ActionEvent e) {
						currentColor = JColorChooser.showDialog(SaigonTechNxtStudio.this, "Chọn màu sắc", currentColor);
						//Save color to wall color collection to extract walls
						if(isWall) wallColors.add(currentColor);
					}
				});
			}
			
			if (!wallColors.contains(currentColor) && isWall) wallColors.add(currentColor);

		}
		
		smartPanel.repaint();
		smartPanel.revalidate();		
	}
	
	private class UploadFileListener implements ActionListener {
		
		private SaigonTechNxtStudio studio;
		
		public UploadFileListener(SaigonTechNxtStudio studio) {
			this.studio = studio;
		}
		
		@Override
		public void actionPerformed(ActionEvent event) {
			JButton button = (JButton) event.getSource();
			//Only handling in Editor mode only
			if (!isPlaying) {
				if (button == uploadBtn) {
					//Initialize the browser
					JFileChooser chooser = new JFileChooser();
					//Looking 4 image types: JPEG, GIF, PNG, BITMAP
				    FileNameExtensionFilter filter = new FileNameExtensionFilter("Images Only", "jpg", "gif", "png", "bmp");
				    //Apply file extension filter
				    chooser.setFileFilter(filter); 
				    //No other is accepted 
				    chooser.setAcceptAllFileFilterUsed(Boolean.FALSE);
				    //Show browser
				    int returnVal = chooser.showOpenDialog(studio);
				    
				    //Process selected file
				    if(returnVal == JFileChooser.APPROVE_OPTION) {
				    	//User image selection
				    	File selectedFile = chooser.getSelectedFile();
				    	//Obstacles building mode
				    	if (isWall) {
				    		//Build wall from selected image
							Wall wall = new Wall(selectedFile.getAbsolutePath());

							// Add Touch listener to every wall on the board
							wall.addMouseTouchListener(studio, GGMouse.lPress	| GGMouse.lRelease, Boolean.TRUE);
							
							// Store wall to collection
							walls.add(wall);

							// Add new wall to board at default location
							table.addActor(wall, SaigonTechNxtContext.getStartLocation());	
				    	} else if (isPaint) { //Painting mode
				    		//Draw the selected background
				    		panel.drawImage(GGBitmap.getImage(selectedFile.getAbsolutePath()));
				    		
				    		// Update painting
							panel.save();
							// Take the snapshot of current painting						
							bgLayer = snapshotBg(panel);
				    	}
				    }
				}
			}
		}
		
	}
	

	/* (non-Javadoc)
	 * Mouse handler listening events on simulation board
	 * @see ch.aplu.jgamegrid.GGMouseListener#mouseEvent(ch.aplu.jgamegrid.GGMouse)
	 */
	@Override
	public boolean mouseEvent(GGMouse mouse) {		
		// Handle in Editor mode only
		if (!isPlaying) {	
			//Tool tips
        	clearTooltip(mouse);
			switch (mouse.getEvent()) {
			case GGMouse.lDrag:
				// Check whether user selected a actor or not
				if (hotSpot != null && selection != null) {
					// Re-calculate new position of actor
					Location loc = new Location(selection.getLocation().x + (mouse.getX() - hotSpot.x), selection.getLocation().y + (mouse.getY() - hotSpot.y));
					// re-assign previous location
					hotSpot = new Point(mouse.getX(), mouse.getY());
					// Move actor in normal mode only
					if (!isRotate && !isWall && !isDelete) selection.setLocation(loc);
					// Rotate actor in rotate mode follow mouse direction
					else if (isRotate) selection.setDirection(selection.getLocation().getDirectionTo(new Location(mouse.getX(), mouse.getY())));
					
					// Refresh the screen
					table.refresh();
				} else {
					// Painting settings
					if (isWall) {
						//Wall settings
						panel.color(currentColor);
						panel.setLineWidth(12);
                                                SaigonTechNxtContext.setLineWidth(widthSlider.getValue());
					} else if (isPaint) {
						//Paint settings
						panel.color(currentColor);
						panel.setLineWidth(32);
                                                SaigonTechNxtContext.setLineWidth(widthSlider.getValue());
					} else if (isEraser) {
						//Erase settings
						panel.color(currentColor);
						panel.setLineWidth(32);
                                                SaigonTechNxtContext.setLineWidth(widthSlider.getValue());
					} else {
						//Nothing to do
						break;
					}

					// Initialize origin location
					if (hotSpot == null) hotSpot = new Point(mouse.getX(), mouse.getY());

					// Paint the line
					if (hotSpot != null) {
						// Update current mouse pointer
						Point currentPoint = new Point(mouse.getX(), mouse.getY());
						
						//Make line smoothly
						Graphics2D g2 = panel.getContext();
						BasicStroke stroke = new BasicStroke(SaigonTechNxtContext.getLineWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
						g2.setStroke(stroke);
						
						// Draw the wall in real fashion (not in user fashion)
						panel.line(panel.toPixelX(hotSpot.x), panel.toPixelY(hotSpot.y), panel.toPixelX(currentPoint.x), panel.toPixelY(currentPoint.y));
						// Store previous location
						hotSpot = currentPoint;
					}
					
					// Update painting
					panel.save();
					// Take the snapshot of current painting
					if (isWall) snapshot = snapshot(panel);
					if (isPaint || isEraser) bgLayer = snapshotBg(panel);
				}
				break;
			case GGMouse.move:
				// Handle moving in design mode
				// Backup HotSpot
				if (hotSpot != null) slaveHotSpot = hotSpot;
				// Refresh last pointer
				hotSpot = null;
				
				//Update tool tips
				
				//Clean old tool tips
				toolTip1.removeSelf();
				toolTip2.removeSelf();
				
				//Initialize latest ones
				toolTip1 = new TextActor("pixel: " + mouse.getX() + ", " + mouse.getY());
				toolTip2 = new TextActor("cm: " + decimalFormat.format(mouse.getX() * STANDARD_GEAR / GEAR_LENGTH) + 
						", " + decimalFormat.format(mouse.getY() * STANDARD_GEAR / GEAR_LENGTH));
				
				//Overwrite
				table.addActor(toolTip1, new Location(mouse.getX() + 10, mouse.getY()));	
				table.addActor(toolTip2, new Location(mouse.getX() + 10, mouse.getY() + 15));
				
				//Precedence
				table.setPaintOrder(TextActor.class, Wall.class);
				
				break;
			case GGMouse.lDClick:
				// Handle double click in design mode
				// Painting settings
				if (isWall) {
					//Wall settings
					panel.color(currentColor);
					panel.setLineWidth(12);
                                        SaigonTechNxtContext.setLineWidth(widthSlider.getValue());
				} else if (isPaint) {
					//Paint settings
					panel.color(currentColor);
					panel.setLineWidth(32);
                                        SaigonTechNxtContext.setLineWidth(widthSlider.getValue());
				} else if (isEraser) {
					//Erase settings
					panel.color(currentColor);
					panel.setLineWidth(32);
                                        SaigonTechNxtContext.setLineWidth(widthSlider.getValue());
				} else if (isDelete) {
					//Delete current background
					panel.clean();						
				} else {
					//Nothing to do
					break;
				}
				
				if (!isDelete) {
					// Initialize origin location
					if (slaveHotSpot == null) slaveHotSpot = new Point(mouse.getX(), mouse.getY());
					
					// Remind last pointer
					if (hotSpot == null) hotSpot = slaveHotSpot;
					
					// Paint the line
					if (hotSpot != null) {
						// Update current mouse pointer
						Point currentPoint = new Point(mouse.getX(), mouse.getY());
						
						//Make line smoothly
						Graphics2D g2 = panel.getContext();
						BasicStroke stroke = new BasicStroke(SaigonTechNxtContext.getLineWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
						g2.setStroke(stroke);
						
						// Draw the wall in real fashion (not in user fashion)
						panel.line(panel.toPixelX(hotSpot.x), panel.toPixelY(hotSpot.y), panel.toPixelX(currentPoint.x), panel.toPixelY(currentPoint.y));
						// Store previous location
						hotSpot = currentPoint;
					}
				}

				// Update painting
				panel.save();
				// Take the snapshot of current painting
				if (isWall) snapshot = snapshot(panel);
				if (isPaint || isEraser || isDelete) bgLayer = snapshotBg(panel);
				
				// Refresh slaver
				slaveHotSpot = null;
				
				break;
            case GGMouse.leave:
            	//Tool tips
            	clearTooltip(mouse);	
            	break;
			} 
			return Boolean.TRUE;
		}
		return Boolean.FALSE;
	}

	/**
	 * Clear tool tip that follows cursor
	 * @param mouse cursor
	 */
	private void clearTooltip(final GGMouse mouse) {
		//Clean tool tips
		toolTip1.removeSelf();
		toolTip2.removeSelf();
		
		//Empty tool tips
		toolTip1 = new TextActor("");
		toolTip2 = new TextActor("");
		
		//Overwrite
		table.addActor(toolTip1, SaigonTechNxtContext.getStartLocation());	
		table.addActor(toolTip2, SaigonTechNxtContext.getStartLocation());
	}	

	/* (non-Javadoc)
	 * Mouse touch listener implementation
	 * @see ch.aplu.jgamegrid.GGMouseTouchListener#mouseTouched(ch.aplu.jgamegrid.Actor, ch.aplu.jgamegrid.GGMouse, java.awt.Point)
	 */
	@Override
	public void mouseTouched(Actor actor, GGMouse mouse, Point spot) {
		//Handle in Editor mode only
		if (!isPlaying) {
			switch (mouse.getEvent()) {
			case GGMouse.lPress:
				//Store the user selection
				hotSpot = new Point(mouse.getX(), mouse.getY());
				selection = actor;
				break;
			case GGMouse.lRelease:
				//Delete the actor
				if (isDelete) {
					//Remove from memory
					walls.remove(selection);
					//Remove the actor from the editor
					selection.removeSelf();
					//Refresh the screen
					table.refresh();
				}
				//Forget the user selection
				hotSpot = null;
				selection = null;
				break;
			}
		}
	}
	
	/**
	 * Reset status and tracker of toggle buttons except play button (simulation control button) 
	 */
	private void resetToolsBox() {	
		//Reset trackers
		isWall = Boolean.FALSE;
		isRotate = Boolean.FALSE;
		isDelete = Boolean.FALSE;
		isPaint = Boolean.FALSE;
		isEraser = Boolean.FALSE;	
		
		//Reset statuses
		wallBtn.setSelected(Boolean.FALSE);
		rotateBtn.setSelected(Boolean.FALSE);
		deleteBtn.setSelected(Boolean.FALSE);
		paintBtn.setSelected(Boolean.FALSE);
		eraserBtn.setSelected(Boolean.FALSE);
		
	}
	
	/**
	 * Enable or display tools box buttons (both simple and toggle) except play button (simulation control button)
	 * @param enable
	 */
	private void enableToolsBox(boolean enable) {		
		wallBtn.setEnabled(enable);
		rotateBtn.setEnabled(enable);
		deleteBtn.setEnabled(enable);
		paintBtn.setEnabled(enable);
		eraserBtn.setEnabled(enable);
		uploadBtn.setEnabled(enable);
	}
	
	/**
	 * Take a transparency snapshot of current panel 
	 * @param panel current panel
	 * @return image without background
	 */
	private BufferedImage snapshot(GGPanel panel) {
		if (isWall) return imageToBufferedImage(makeAllColorTransparentExcept(panel.getBackgroundImage(), wallColors.toArray(new Color[0])));
		return imageToBufferedImage(makeColorTransparent(panel.getBackgroundImage(), panel.getBgColor()));	
	}
	
	/**
	 * Take a background snapshot of current panel 
	 * @param panel current panel
	 * @return image current background
	 */
	private BufferedImage snapshotBg(GGPanel panel) {
		return imageToBufferedImage(panel.getBackgroundImage());	
	}
	
	/**
	 * Convert an image to a buffered image. Thanks to Corgrath & PhiLho
	 * @param image the image need to convert 
	 * @return new image
	 */
	private static BufferedImage imageToBufferedImage(Image image) {
        BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = bufferedImage.createGraphics();
        g2.drawImage(image, 0, 0, null);
        g2.dispose();
        return bufferedImage;
    }

    /** 
     * Sets the transparency of the given color. Thanks to Corgrath & PhiLho
     * @param im a buffered image
     * @param color color to set to transparency
     * @return new image
     */
    public static Image makeColorTransparent(BufferedImage im, final Color color) {
        ImageFilter filter = new RGBImageFilter() {
                // the color we are looking for... Alpha bits are set to opaque
                public int markerRGB = color.getRGB() | 0xFF000000;

                public final int filterRGB(int x, int y, int rgb) {
                        if ((rgb | 0xFF000000) == markerRGB) {
                                // Mark the alpha bits as zero - transparent
                                return 0x00FFFFFF & rgb;
                        } else {
                                // nothing to do
                                return rgb;
                        }
                }
        };
        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }
    
    /**
     * Set the transparency of all colors except the given color
     * @param im a buffered image
     * @param color color to set to opaque
     * @return new image
     */
    public static Image makeAllColorTransparentExcept(BufferedImage im, final Color... colors) {
        ImageFilter filter = new RGBImageFilter() {   
        	
                public final int filterRGB(int x, int y, int rgb) {
                	for (Color color : colors)
                        if ((rgb | 0xFF000000) == (color.getRGB() | 0xFF000000)) 
                        	return rgb;  // nothing to do 
                        return 0x00FFFFFF & rgb;  // Mark the alpha bits as zero - transparent                                        
                }
        };
        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }
    
    /** 
     * Sets the transparency of the given colors. 
     * @param im a buffered image
     * @param colors colors to set to transparency
     * @return new image
     */
    public static Image makeColorsTransparent(BufferedImage im, final Color... colors) {
        ImageFilter filter = new RGBImageFilter() {
                public final int filterRGB(int x, int y, int rgb) {
                	    for (Color color : colors)
                	    	if ((rgb | 0xFF000000) == (color.getRGB() | 0xFF000000)) 
	                                // Mark the alpha bits as zero - transparent
	                                return 0x00FFFFFF & rgb;
                	    
                        return rgb; // nothing to do
                 }
        };
        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }
    
    public boolean isSim() {
    	return switchBtn.isSelected();
    }
	
	/**
	 * Class represent wall as an obstacle
	 * @author Saigon Institute of Technology
	 *
	 */
	private class Wall extends Obstacle {
		
		public Wall() {
			super("sprites/wall.png");			
		}
		
		public Wall(String imageName) {
			super(imageName);
		}
		
		public Wall(BufferedImage bi) {
			super(bi);
		}		
	}
	
	/**
	 * Present a game grid with customizable behaviors by overwriting
	 * supporting multi-robots environment
	 * @author Saigon Institute of Technology
	 *
	 */
	private class MyGameGrid extends GameGrid {
		public MyGameGrid(int nbhorzcells, int nbvertcells, int cellsize, Color gridcolor, String imageName, boolean isNavigationBar, int nbrotatablesprites, boolean b) {			
			super(nbhorzcells, nbvertcells, cellsize, gridcolor, imageName, isNavigationBar, nbrotatablesprites, b);
		}

		@Override
		public void show() {
			//do nothing
		}
	}
	
	/**
	 * Display text on LCD
	 * @param text display
	 */
	public void displayLCD(final String text) {
		EventQueue.invokeLater(new Runnable() {

			@Override
			public void run() {
				lcdSim.setText(text);
			}});
	}
	
	/**
	 * Save operation handler
	 * @author Saigon Institute of Technology
	 *
	 */
	private class SaveListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			if (mode == KitMode.ROBOT_DESIGN) { //ROBOT CONFIGURATION STUDIO MODE 
				if (RobotConfigurationDAO.existed(workingConfiguration.name)) 
					RobotConfigurationDAO.delete(workingConfiguration.name);//Delete to update later
				
				RobotConfigurationDAO.save(workingConfiguration);
				
				loadSupportRobotConfigurations();
			}
		}	
	}
	
	/**
	 * Load supported robot configuration
	 */
	private void loadSupportRobotConfigurations() {
		//Load configuration
		supportedRobots.removeAll(); //Clean & clear
		
		for (String each : RobotConfigurationDAO.getSupportedRobot()) {
			JMenuItem eachRobotConfig = new JMenuItem(each);	
			eachRobotConfig.setForeground(Color.white);
			eachRobotConfig.addActionListener(new LoadSelectedRobotListener());
			supportedRobots.add(eachRobotConfig);			
		}
		
		this.supportedRobots.getPopupMenu().setPopupSize(200, 25 * RobotConfigurationDAO.getSupportedRobot().length);
	}
	
	/**
	 * Load selection of robot configuration
	 * @author Saigon Institute of Technology
	 *
	 */
	private class LoadSelectedRobotListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			JMenuItem src = (JMenuItem) e.getSource();
			RobotConfiguration selection = RobotConfigurationDAO.load(src.getText());
			if (selection != null) {
				workingConfiguration = selection;
				if (workingConfiguration instanceof CarRobot) carConfigMenuItem.doClick();
				else robotConfigMenuItem.doClick();
			}
		}
		
	}
	
	static enum KitMode {
		SIMULATION,
		ROBOT_DESIGN
	}
	
//	---------------------------------------------- Robot Configuration Mode --------------------------------------
	
	//Toolset
	private JPanel designToolset = new JPanel();	
	
	//Design board
	private JPanel designBoard = new MyPanel(); 	
	
	//Extension
	private JPanel extensionBoard = new JPanel();
	
	//Working configuration document
	private RobotConfiguration workingConfiguration = CAR_ROBOT;	
	
	//Running mode
	private KitMode mode = KitMode.SIMULATION;
	
	/**
	 * Robot Configuration Mode GUI initialization
	 */
	private void setupRobotDesignMode() {
		//Add tools box to controller panel with border title
		setupPanel(this.ctrPanel, this.designToolset, "Bộ phận");
		
		//Set background & size
//		this.designToolset.setBackground(Color.white);
		this.designToolset.setPreferredSize(new Dimension(130, 400));
		//Initialize layout
		this.designToolset.setLayout(new GridBagLayout());

		//Add design board to simulation panel with border title
		setupPanel(this.simPanel, this.designBoard, "Thiết kế Robot");
		
		//Set background & size
		this.designBoard.setBackground(Color.white);
		this.designBoard.setPreferredSize(new Dimension(775, 400));		
		
		//Add extension to smart panel
		setupPanel(this.smartPanel, this.extensionBoard, "Vị trí Cổng");		
		
		//Set background & size
		this.extensionBoard.setPreferredSize(new Dimension(775, 50));
//		this.extensionBoard.setBackground(Color.white);
		
		//Change mode to Robot Configuration Studio
		mode = KitMode.ROBOT_DESIGN;
		
		//Disable ruler system
		horizontalRuler.setVisible(mode == KitMode.SIMULATION);	
		verticalRuler.setVisible(mode == KitMode.SIMULATION);
	}
	
	/**
	 * Robot Configuration Module
	 * @author Saigon Institute of Technology
	 *
	 */
	private class RobotConfigurationActionListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			if (workingConfiguration instanceof CarRobot) workingConfiguration = DEFAULT_ROBOT; //Oerwrite
			//Switch to designer
			initConfigurationDesigner();		
		}		
	}
	
	/**
	 * Car Configuration Module
	 * @author Saigon Institute of Technology
	 *
	 */
	private class CarConfigurationActionListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			if(!(workingConfiguration instanceof CarRobot)) workingConfiguration = CAR_ROBOT;	
			//Switch to designer
			initConfigurationDesigner();
		}		
	}
	
	/**
	 * Initialize designer mode
	 */
	private void initConfigurationDesigner() {
		studioWidth = STUDIO_WIDTH;
		studioHeight = STUDIO_HEIGHT;
		//Preferred size			 
		setSize(studioWidth, studioHeight);
		//Setup GUI components layout
		setupRobotDesignMode();
		
		//Load working document (Robot Configuration)
		loadRobotConfiguration(workingConfiguration);
	}		
	
	//Another holders for items in RobotConfiguration
	private JTextField nameTextField = new JTextField(10);
	private JTextField descriptionTextArea = new JTextField(10);
	private JTextField axeSizeTextField = new JTextField(5);
	private JTextField wheelDiameterTextField = new JTextField(5);
	
	//Action Listener text fields that held name, description, axe size and wheel diameter
	private KeyListener generalConfigListener = new KeyListener() {
		
		@Override
		public void keyTyped(KeyEvent e) {}
		
		@Override
		public void keyReleased(KeyEvent e) {
			updateWorkingRobotConfiguration();
		}
		
		@Override
		public void keyPressed(KeyEvent e) {}
	};

	//Caches for Robot Configuration: Port - Part mapping
	private List<JComboBox<RobotPart>> robotPart_Port = new ArrayList<>();
	//Caches for Robot Configuration: Port - Location mapping
	private List<JComboBox<RobotPartLocation>> robotPort_Loc = new ArrayList<>();
	
	/**
	 * Update working configuration ONLY includes: wheel diameter, axe size, description & name 
	 */
	private void updateWorkingRobotConfiguration() {
		String name = nameTextField.getText().trim();
		String description = descriptionTextArea.getText().trim();
		String axeSize = axeSizeTextField.getText().trim();
		String wheelDiameter = wheelDiameterTextField.getText().trim();
		
		if (isDouble(axeSize)) workingConfiguration.axeSize = Double.parseDouble(axeSize);
		if (isDouble(wheelDiameter)) workingConfiguration.wheelDiameter = Double.parseDouble(wheelDiameter);
		
		if (!name.isEmpty()) workingConfiguration.name = name;
		if (!description.isEmpty()) workingConfiguration.description = description;
	}
	
	/**
	 * Check whether a string is a number of not
	 * @param numberInString String presents a number
	 * @return boolean
	 */
	private boolean isDouble(String numberInString) {
		try {
			Double.parseDouble(numberInString);
			return true;
		} catch (Exception ex) {
			return false;
		}
	}
	
	
	/**
	 * Maintain Robot Part (Port - Part)
	 * @author Saigon Institute of Technology
	 *
	 */
	private class UpdateRobotPartListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			//Store running configuration: ports, parts, locs
			List<RobotPort> ports = new ArrayList<>();
			List<RobotPart> parts = new ArrayList<>();
			List<RobotPartLocation> locs = new ArrayList<>();
			
			//Extract Port - Part
			for (RobotPort port : RobotPort.values()) {
				RobotPart part = (RobotPart) robotPart_Port.get(port.ordinal()).getSelectedItem();
				if (part!= null) {
					ports.add(port);
					parts.add(part);
					
					//Check whether existing part or not
					boolean isNew = Boolean.TRUE;//New part is Default
					for (PartConfiguration configuration : workingConfiguration.items) {
						if (configuration.port == port) { //Existing part
							locs.add(configuration.loc); //Existing location
							isNew = Boolean.FALSE;
							break;
						}
					}
					if (isNew) { //New part
						RobotPartLocation loc = RobotPartLocation.LEFT_CENTER;
						locs.add(loc);//New location
					}					
				}				
			}
			
			//Update working configuration (Robot Port - Part only)			
			workingConfiguration.items.clear();//Remove old configurations
			for (int i = 0; i < ports.size(); i++) {
				//Update latest configurations
				workingConfiguration.items.add(new PartConfiguration(ports.get(i), parts.get(i), locs.get(i)));				
			}
			
			//Reload Robot Configuration
			loadRobotConfiguration(workingConfiguration);
		}		
	}
	
	/**
	 * Maintain Robot Part Location (Port - Location)
	 * @author Saigon Institute of Technology
	 *
	 */
	private class UpdateRobotPartLocationListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			//Store running configuration: ports, parts, locs
			List<RobotPort> ports = new ArrayList<>();
			List<RobotPart> parts = new ArrayList<>();
			List<RobotPartLocation> locs = new ArrayList<>();
			
			//Extract Port - Part
			for (RobotPort port : RobotPort.values()) {
				RobotPart part = (RobotPart) robotPart_Port.get(port.ordinal()).getSelectedItem();
				if (part!= null) {
					ports.add(port);
					parts.add(part);
				}
			}
			
			//Extract Port - Locations
			for (int i = 0; i < ports.size(); i++) {
				RobotPartLocation loc = (RobotPartLocation) robotPort_Loc.get(i).getSelectedItem();
				locs.add(loc);
			}
			
			//Update working configuration (Robot Part Location Only) 
			workingConfiguration.items.clear();//Remove old configurations
			for (int i = 0; i < ports.size(); i++) {
				//Update latest configurations
				workingConfiguration.items.add(new PartConfiguration(ports.get(i), parts.get(i), locs.get(i)));				
			}
			
			//Reload Robot Configuration
			loadRobotConfiguration(workingConfiguration);
		}		
	}

	/**
	 * Load configuration on Visual Studio: Design board, Tool set board & extension board
	 * @param robotConfiguration configuration
	 */
	private void loadRobotConfiguration(final RobotConfiguration robotConfiguration) {
		//Clean & clear
		designToolset.removeAll();
		extensionBoard.removeAll();
		designBoard.removeAll();		
		
		//Clean & clear out-of-dated caches
		robotPart_Port.clear();
		robotPort_Loc.clear();
		
		//Load Robot Configuration visually on Design Board
		((MyPanel) designBoard).loadRobotConfiguration(robotConfiguration);	
		
		//Layout calculation (automatically)
		extensionBoard.setLayout(new FlowLayout(FlowLayout.LEFT, 17, 4));
		
		//General robot design mode doesn't display location panel
		if (robotConfiguration instanceof CarRobot) smartPanel.setVisible(Boolean.TRUE);
		else smartPanel.setVisible(Boolean.FALSE);		
		
		int column = 0;
		//Load UI selections
		for (RobotPort port : RobotPort.values()) {	
			//Load Port-Part selector
			JComboBox<RobotPart> robotPartSelector = new JComboBox<>(RobotPart.values());
			//Port & Part validation
			//Motors only connect to A, B or C port
			if (port == RobotPort.A || port == RobotPort.B || port == RobotPort.C) {
				robotPartSelector.removeItem(RobotPart.LIGHT_SENSOR);
				robotPartSelector.removeItem(RobotPart.TOUCH_SENSOR);
				robotPartSelector.removeItem(RobotPart.SONIC_SENSOR);
				robotPartSelector.removeItem(RobotPart.SOUND_SENSOR);
			} else robotPartSelector.removeItem(RobotPart.MOTOR); //Only sensors supported by S1-S12
			
			robotPartSelector.addItem(null); //Empty Option
			robotPartSelector.setSelectedIndex(robotPartSelector.getItemCount() - 1); //Default choice
			
			//Update value of each Port-Part selector (Not default choice)
			for (PartConfiguration partConfiguration : robotConfiguration.items) {
				if (partConfiguration.port == port) {
					robotPartSelector.setSelectedItem(partConfiguration.part);//Update selection	
					
					//Load Port - Location selections 
					JComboBox<RobotPartLocation> robotLocSelector = new JComboBox<>(RobotPartLocation.values());
					robotLocSelector.setSelectedItem(partConfiguration.loc);// Update selection
					
					//Show
					JLabel plabel = new JLabel(port.name());
					extensionBoard.add(plabel);
					extensionBoard.add(robotLocSelector);
					
					//Add Part Location  to handler
					robotPort_Loc.add(robotLocSelector);
					
					//Hide Gear (Car Robot Only)
					if (robotConfiguration instanceof CarRobot) {
						CarRobot carRobot = (CarRobot) robotConfiguration;
						
						if (carRobot.leftMotor.port == port || carRobot.rightMotor.port == port) {
							robotLocSelector.setEnabled(Boolean.FALSE); //Gear dedicated
							extensionBoard.remove(robotLocSelector);
							extensionBoard.remove(plabel);
						}
					}
					
					//Not need to find anymore
					break;
				}				
			}			
			
			//Show
			GridBagConstraints constraints = new GridBagConstraints();
			
			constraints.anchor = GridBagConstraints.FIRST_LINE_START;
			constraints.fill = GridBagConstraints.HORIZONTAL;			
			constraints.weighty = 0.5;			
			
			constraints.gridx = 0;
			constraints.gridy = column;
			
			JLabel portLabel = new JLabel(port.name());			
			designToolset.add(portLabel, constraints);	
			
			constraints.gridx = 1;
			constraints.gridy = column;			
			designToolset.add(robotPartSelector, constraints);
			
			column++;
			
			//Add Port - Part to handler
			robotPart_Port.add(robotPartSelector);	
			
			//Non Car Robot
			if (!(robotConfiguration instanceof CarRobot)) {//Only Physical port supported
				if (port.ordinal() > RobotPort.S4.ordinal()) robotPartSelector.setEnabled(Boolean.FALSE);				
			}
			
			//Hide Gear (Car Robot Only)
			if (robotConfiguration instanceof CarRobot) {
				CarRobot carRobot = (CarRobot) robotConfiguration;
				
				if (carRobot.leftMotor.port == port || carRobot.rightMotor.port == port) {
					robotPartSelector.setEnabled(Boolean.FALSE); //Gear dedicated
				}
			}

		}		
		
		//Show another UIs
		designBoard.setLayout(new BorderLayout());
		JPanel subPanel = null;
		if (robotConfiguration instanceof CarRobot)	subPanel = new JPanel(new GridLayout(7, 2));
		else subPanel = new JPanel(new GridLayout(3, 2));
		//Background setting
		subPanel.setBackground(Color.white);
		designBoard.add(subPanel, BorderLayout.SOUTH);
		
		//Car Robot Support
		if (robotConfiguration instanceof CarRobot) {
			
			final CarRobot carRobot = (CarRobot) robotConfiguration;
			
			//Wheel Diameter
			JLabel wheelDiameterLabel = new JLabel("Đường kính bánh xe (mm):");
			wheelDiameterTextField.setText(robotConfiguration.wheelDiameter + "");
			subPanel.add(wheelDiameterLabel);
			subPanel.add(wheelDiameterTextField);
			
			//AXE size
			JLabel axeLabel = new JLabel("Chiều dài trục bánh xe (mm):");			
			axeSizeTextField.setText(robotConfiguration.axeSize + "");			
			subPanel.add(axeLabel);
			subPanel.add(axeSizeTextField);
			
			//Left & right motors
			JLabel leftMotor = new JLabel("Mô tơ bên trái:");
			JComboBox<RobotPort> leftMotorSelector = new JComboBox<RobotPort>(new RobotPort[] {RobotPort.A, RobotPort.B, RobotPort.C});
			//Update Selection
			leftMotorSelector.removeItem(carRobot.rightMotor.port);
			leftMotorSelector.setSelectedItem(carRobot.leftMotor.port);			
			subPanel.add(leftMotor);
			subPanel.add(leftMotorSelector);
			//Add Listener
			leftMotorSelector.addActionListener(new ActionListener() {
				
				@Override
				public void actionPerformed(ActionEvent e) {
					JComboBox<RobotPort> selector =  (JComboBox<RobotPort>) e.getSource();
					carRobot.setLeftMotorPort((RobotPort) selector.getSelectedItem());
					loadRobotConfiguration(robotConfiguration);
				}
			});
			
			JLabel rightMotor = new JLabel("Mô tơ bên phải:");	
			JComboBox<RobotPort> rightMotorSelector = new JComboBox<RobotPort>(new RobotPort[] {RobotPort.A, RobotPort.B, RobotPort.C});
			//Update Selection
			rightMotorSelector.removeItem(carRobot.leftMotor.port);
			rightMotorSelector.setSelectedItem(carRobot.rightMotor.port);			
			subPanel.add(rightMotor);
			subPanel.add(rightMotorSelector);
			//Add Listener
			rightMotorSelector.addActionListener(new ActionListener() {
				
				@Override
				public void actionPerformed(ActionEvent e) {
					JComboBox<RobotPort> selector =  (JComboBox<RobotPort>) e.getSource();
					carRobot.setRightMotorPort((RobotPort) selector.getSelectedItem());
					loadRobotConfiguration(robotConfiguration);
				}
			});
			
		}
		
		//Description
		JLabel desLabel = new JLabel("Mô tả:");			
		descriptionTextArea.setText(robotConfiguration.description);
		subPanel.add(desLabel);
		subPanel.add(descriptionTextArea);
		
		//Name
		JLabel nameLabel = new JLabel("Tên cấu hình:");			
		nameTextField.setText(robotConfiguration.name + "");			
		subPanel.add(nameLabel);
		subPanel.add(nameTextField);
		
		//Add Update Part Listeners
		for (JComboBox<RobotPart> box : robotPart_Port) {
			box.addActionListener(new UpdateRobotPartListener());
		}
		
		//Add Update Location Listeners
		for (JComboBox<RobotPartLocation> box : robotPort_Loc) {
			box.addActionListener(new UpdateRobotPartLocationListener());
		}		
		
		//Added General Listeners (Once Only)		
		if (nameTextField.getKeyListeners().length == 0) nameTextField.addKeyListener(generalConfigListener);
		if (descriptionTextArea.getKeyListeners().length == 0) descriptionTextArea.addKeyListener(generalConfigListener);
		if (axeSizeTextField.getKeyListeners().length == 0) axeSizeTextField.addKeyListener(generalConfigListener);
		if (wheelDiameterTextField.getKeyListeners().length == 0) wheelDiameterTextField.addKeyListener(generalConfigListener);		
		
		//Save configuration
		JButton saveRobotConfigurationButn = new JButton("Lưu Robot");		
		subPanel.add(saveRobotConfigurationButn);
		
		saveRobotConfigurationButn.addActionListener(new ActionListener() {			
			@Override
			public void actionPerformed(ActionEvent e) {
				saveMenuItem.doClick();			
			}
		});
		
		//Delete configuration
		JButton deleteRobotConfigurationButn = new JButton("Xóa Robot");		
		subPanel.add(deleteRobotConfigurationButn);
		
		deleteRobotConfigurationButn.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				RobotConfigurationDAO.delete(workingConfiguration.name);
				loadSupportRobotConfigurations();
				simulationModeItem.doClick();				
			}
		});
		
		
		//Re-fresh
		designToolset.validate();
		extensionBoard.validate();
		designBoard.validate();
		//Re-paint
		designBoard.repaint();
	}	

	/**
	 * Simulation Mode
	 * @author Saigon Institute of Technology
	 *
	 */
	private class SimulationModeActionListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			studioWidth = STUDIO_WIDTH;
			studioHeight = STUDIO_HEIGHT;
			//Preferred size
			setSize(studioWidth, studioHeight);
			//Setup GUI components Layout
			setupMainMode(); 
			//Change mode to Simulation
			mode = KitMode.SIMULATION;
			
			//Enable ruler system
			horizontalRuler.setVisible(mode == KitMode.SIMULATION);		
			verticalRuler.setVisible(mode == KitMode.SIMULATION);	
		}		
	}
	
	//Robot Part Location Sprites
	private static final String[] FRONT_SPRITES = new String[] {"", "sprites/lightsensor_front.gif", "sprites/touchsensor_front.gif", "sprites/ultrasonicsensor_front.png", "sprites/soundsensor.gif"};
	private static final String[] REAR_SPRITES = new String[] {"", "sprites/lightsensor_back.gif", "sprites/touchsensor_back.gif", "sprites/ultrasonicsensor_back.png", "sprites/soundsensor.gif"};
	private static final String[] LEFT_SPRITES = new String[] {"sprites/leftmotor.gif", "sprites/lightsensor_left.gif", "sprites/touchsensor_left.gif", "sprites/ultrasonicsensor_left.png", "sprites/soundsensor.gif"};
	private static final String[] RIGHT_SPRITES = new String[] {"sprites/rightmotor.gif", "sprites/lightsensor_right.gif", "sprites/touchsensor_right.gif", "sprites/ultrasonicsensor_right.png", "sprites/soundsensor.gif"};

	
	//Robot Part Locations
	static enum RobotPartLocation {		
		FRONT_LEFT(326, 44, FRONT_SPRITES),
		FRONT_CENTER(326, 97, FRONT_SPRITES),
		FRONT_RIGHT(326, 152, FRONT_SPRITES),
		REAR_LEFT(82, 44, REAR_SPRITES),
		REAR_CENTER(82, 97, REAR_SPRITES),
		REAR_RIGHT(82, 152, REAR_SPRITES),
		LEFT_FRONT(286, 12, LEFT_SPRITES),
		LEFT_CENTER(205, 12, LEFT_SPRITES),
		LEFT_REAR(124, 12, LEFT_SPRITES),
		RIGHT_FRONT(286, 185, RIGHT_SPRITES),
		RIGHT_CENTER(205, 185, RIGHT_SPRITES),
		RIGHT_REAR(124, 185, RIGHT_SPRITES);
		
		private int x_loc;
		private int y_loc;
		private String[] sprites;		
		
		private RobotPartLocation(int x_loc, int y_loc, String... sprites) {
			this.x_loc = x_loc;
			this.y_loc = y_loc;
			this.sprites = sprites;
		}		
	}
	
	//Robot Ports
	static enum RobotPort {
		A,
		B,
		C,
		S1,
		S2,
		S3,
		S4,
		S5,
		S6,
		S7,
		S8,
		S9,
		S10,
		S11,
		S12		
	}
	
	//Robot Parts
	static enum RobotPart {		
		MOTOR("sprites/motoricon.png"),
		LIGHT_SENSOR("sprites/lighticon.png"),
		TOUCH_SENSOR("sprites/touchicon.png"),
		SONIC_SENSOR("sprites/sonaricon.png"),
		SOUND_SENSOR("sprites/soundicon.png");
		
		private String icon;
		
		private RobotPart(String iconPath) {
			this.icon = iconPath;
		}
	}
	
	//DEFAULT ROBOT configuration (without extension part)
	private static final RobotConfiguration DEFAULT_ROBOT = new RobotConfiguration("DEFAULT_ROBOT");
	
	private static final RobotConfiguration CAR_ROBOT = new CarRobot("CAR_ROBOT");
	
	static {
		//Motors
		CAR_ROBOT.items.add(new PartConfiguration(RobotPort.A, RobotPart.MOTOR, RobotPartLocation.LEFT_FRONT));
		CAR_ROBOT.items.add(new PartConfiguration(RobotPort.B, RobotPart.MOTOR, RobotPartLocation.RIGHT_FRONT));		
		
		CAR_ROBOT.description = "SaigonTech Car Robot!";
		DEFAULT_ROBOT.description = "SaigonTech Default Robot!";		
	}
	
	
	/**
	 * Robot Configuration including construction info to build a robot 
	 * @author Saigon Institute of Technology
	 *
	 */
	private static class RobotConfiguration {
		
		//Unique name for configuration
		private String name;
		
		//Robot configuration description
		private String description;
		
		//Size between wheels (mm)
		private double axeSize = 120;
		
		//Unit in millimeter (mm)
		private double wheelDiameter = 56;
		
		//List of extension parts
		private List<PartConfiguration> items = new ArrayList<>();
		
		//Remember name must unique
		public RobotConfiguration(String name) {			
			this.name = name;
		}		
	}
	
	
	/**
	 * Robot Part Configuration presenting each robot extension part information
	 * @author Saigon Institute of Technology
	 *
	 */
	private static final class PartConfiguration {
		
		//Port that the robot part connecting to (S1 - S12, A, B, C) 
		private RobotPort port;
		
		//Which robot extension part connected (Motors & sensors)
		private RobotPart part;
		
		//Location of robot extension (part + port): front, left, rear, right ...
		private RobotPartLocation loc;		
		
		//Part Configuration require 1 Port, 1 Part and 1 Location
		public PartConfiguration(RobotPort port, RobotPart part, RobotPartLocation loc) {			
			this.port = port;
			this.part = part;
			this.loc = loc;
		}		
	}
	
	
	/**
	 * Panel that support painting for (Robot Configuration Visual studio)
	 * @author Saigon Institute of Technology
	 *
	 */
	private class MyPanel extends JPanel {
		
		private RobotConfiguration robotConfiguration;
	
		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
			
			//Draw robot visually on design screen
			if (robotConfiguration != null) {
				//Clean & clear					
				//Add brick
				BufferedImage brick = GGBitmap.getScaledImage("sprites/nxtrobot.gif", 6, 0);			
				g.drawImage(brick, 100, 20, null);					
				
				//General robot design mode doesn't display location panel
				if (robotConfiguration instanceof CarRobot) 				
				//Add Robot Parts
				for (PartConfiguration part : robotConfiguration.items) {
					if (!part.loc.sprites[part.part.ordinal()].isEmpty()) {
						BufferedImage partImg = GGBitmap.getScaledImage(part.loc.sprites[part.part.ordinal()], 6, 0);
						if (part.part == RobotPart.MOTOR) {
							if (part.loc.y_loc < 100) g.drawImage(partImg, part.loc.x_loc - 174, part.loc.y_loc + 8, null);
							else g.drawImage(partImg, part.loc.x_loc - 174, part.loc.y_loc - 165, null);
						}
						else g.drawImage(partImg, part.loc.x_loc, part.loc.y_loc, null);
					}					
				}				
			}			
		}		

		public void loadRobotConfiguration(RobotConfiguration robotConfiguration) {
			this.robotConfiguration = robotConfiguration;
		}
	}
	
	//SaigonTech NXTKit Configuration Store 
	private static final String DATABASE_FILE_PATH = "sgtkit-robot.conf";	
	
	/***********************************************Robot Configuration Utilization******************************************/
	
	/**
	 * Support CRUP operations on Robot Configurations
	 * @author Saigon Institute of Technology
	 * Not Recommended for using in production (need to improve to use in production env) 
	 */
	private static class RobotConfigurationDAO {
		
		private static final String START_GENERAL_ROBOT_TAG = "[START-ROBOT]";
		private static final String END_GENERAL_ROBOT_TAG = "[END-ROBOT]";
		private static final String START_CAR_ROBOT_TAG = "[START-CAR]";
		private static final String END_CAR_ROBOT_TAG = "[END-CAR]";

		
		/**
		 * Delete a robot configuration
		 * @param name of robot config need to remove		 
		 */
		public static void delete(String name) {
			RobotConfiguration[] configurations = load();
			
			Path database = Paths.get(DATABASE_FILE_PATH);
			try {
				Files.deleteIfExists(database);
			} catch (IOException e) {}
			
			for (RobotConfiguration each : configurations) if (!each.name.equalsIgnoreCase(name)) save(each);
		}
		
		/**
		 * Save list of configurations to store
		 * @param configurations list need to save
		 * @return whether sucess or not
		 */
		public static boolean save(RobotConfiguration[] configurations) {
			for (RobotConfiguration each : configurations) if (!save(each)) return Boolean.FALSE;
			return Boolean.TRUE;
		}
		
		/**
		 * Persist a configuration to store
		 * @param configuration robot configuration to save to store
		 * @return success or not
		 */
		public static boolean save(RobotConfiguration configuration) {
			String startTag = START_GENERAL_ROBOT_TAG;
			String endTag = END_GENERAL_ROBOT_TAG;
			
			if (configuration instanceof CarRobot) {
				startTag = START_CAR_ROBOT_TAG;
				endTag = END_CAR_ROBOT_TAG;
			}
			
			Path database = Paths.get(DATABASE_FILE_PATH);			
			
			try (BufferedWriter writer = Files.newBufferedWriter(database, Charset.defaultCharset(), StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
				writer.append(startTag);
				writer.newLine();
				
				//Car Robot Support
				if (configuration instanceof CarRobot) {
					//Left Motor
					writer.append(((CarRobot) configuration).leftMotor.port.name());
					writer.newLine();
					
					//Right Motor
					writer.append(((CarRobot) configuration).rightMotor.port.name());
					writer.newLine();					
				} else {
					writer.append("");
					writer.newLine();
					
					writer.append("");
					writer.newLine();
				}
				
				writer.append(configuration.name);
				writer.newLine();
				
				writer.append(configuration.description);
				writer.newLine();
				
				writer.append(configuration.axeSize + "");
				writer.newLine();
				
				writer.append(configuration.wheelDiameter + "");
				writer.newLine();
				
				for (PartConfiguration part : configuration.items) {
					writer.append(part.port + ", " + part.part + ", " + part.loc);
					writer.newLine();
				}
				
				writer.append(endTag);
				writer.newLine();
				
				writer.flush();
				
				return Boolean.TRUE;
			} catch (Exception e) { return Boolean.FALSE; }
		}
		
		
		/**
		 * Load all configurations from store
		 * @return list of configurations
		 */
		public static RobotConfiguration[] load() {			
			
			List<RobotConfiguration> configurations = new ArrayList<>();
			
			Path database = Paths.get(DATABASE_FILE_PATH);
			
			RobotConfiguration configuration = null;
			
			if (Files.exists(database)) {
				try {
					List<String> lines = Files.readAllLines(database, Charset.defaultCharset());
					
					for (int i = 0; i < lines.size(); i++) {
						if (lines.get(i).equals(START_GENERAL_ROBOT_TAG) || lines.get(i).equals(START_CAR_ROBOT_TAG)) {							
							
							//Car robot Support
							RobotPort leftMotorPort = null;
							RobotPort rightMotorPort = null;
							
							if (lines.get(i).equals(START_CAR_ROBOT_TAG)) {
								i++;
								//Left Motor
								leftMotorPort = RobotPort.valueOf(lines.get(i));
								i++;
								//Right Motor
								rightMotorPort = RobotPort.valueOf(lines.get(i));
							} else {
								//General Robot: do nothing!
								i += 2;
							}
							
							i++; //Next line
							if (leftMotorPort != null && rightMotorPort != null) configuration = new CarRobot(lines.get(i), leftMotorPort, rightMotorPort);
							else configuration = new RobotConfiguration(lines.get(i));
							
							i++; //Next line
							configuration.description = lines.get(i);
							i++; //Next line
							configuration.axeSize = Double.parseDouble(lines.get(i));
							i++; //Next line
							configuration.wheelDiameter = Double.parseDouble(lines.get(i));
							i++; //Next line
							while (!(lines.get(i).equals(END_GENERAL_ROBOT_TAG) || lines.get(i).equals(END_CAR_ROBOT_TAG))) {
								String[] data = lines.get(i).split(",");
								configuration.items.add(new PartConfiguration(RobotPort.valueOf(data[0].trim()), 
										RobotPart.valueOf(data[1].trim()), RobotPartLocation.valueOf(data[2].trim())));
								i++; //Next line
							}
							configurations.add(configuration); //Completed
							configuration = null; //Release
						}						
					}
				} catch (IOException e) {}
			}
			
			return configurations.toArray(new RobotConfiguration[0]);			
		}
		
		/**
		 * Load RobotConfiguration with its name
		 * @param name config name
		 * @return RobotConfiguration
		 */
		public static RobotConfiguration load(String name) {
			RobotConfiguration ret = null;
			for (RobotConfiguration each : load()) 
				if (each.name.equalsIgnoreCase(name)) return each;
			
			return ret;
		}
		
		/**
		 * List of Robot configuration names
		 * @return names
		 */
		public static String[] getSupportedRobot() {
			List<String> robots = new ArrayList<>();
			for (RobotConfiguration each : load()) robots.add(each.name);
			return robots.toArray(new String[0]);
		}
		
		/**
		 * Check whether a name existed
		 * @param name name of robot configuration
		 * @return existed or not
		 */
		public static boolean existed(String name) {
			for (String each : getSupportedRobot())
				if (each.equalsIgnoreCase(name)) return Boolean.TRUE;
			return Boolean.FALSE;
		}
	}
	
	/*****************************************************  Car Robot Support ***************************************************/
	
	/**
	 * Car Robot Configuration requires Left & Right motor. So it should be a subclass derived from RobotConfiguration
	 * @author Saigon Institute of Technology
	 *
	 */
	private static class CarRobot extends RobotConfiguration {

		private PartConfiguration leftMotor;
		private PartConfiguration rightMotor;
		
		public CarRobot(String name, RobotPort leftMotorPort, RobotPort rightMotorPort) {
			super(name);			
			this.leftMotor = new PartConfiguration(leftMotorPort, RobotPart.MOTOR, RobotPartLocation.LEFT_FRONT);
			this.rightMotor = new PartConfiguration(rightMotorPort, RobotPart.MOTOR, RobotPartLocation.RIGHT_FRONT);
			
			super.items.add(leftMotor);
			super.items.add(rightMotor);
		}
		
		public CarRobot(String name) {
			this(name, RobotPort.A, RobotPort.B);
		}	
		
		public void setLeftMotorPort(RobotPort port) {
			leftMotor.port = port;
			for (PartConfiguration each : super.items) 
				if (each.part == RobotPart.MOTOR && each.loc == RobotPartLocation.LEFT_FRONT)
					each.port = port;
		}
		
		public void setRightMotorPort(RobotPort port) {
			rightMotor.port = port;
			for (PartConfiguration each : super.items) 
				if (each.part == RobotPart.MOTOR && each.loc == RobotPartLocation.RIGHT_FRONT)
					each.port = port;
		}
	}
	
	/**
	 * Panel support background
	 * @author Saigon Institute of Technology
	 *
	 */
	private class BackgroundPanel extends JPanel {
		private Image image;
		
		public BackgroundPanel(String bgImg) {
			this.image = GGBitmap.getImage(bgImg);
		}
		
		@Override
		protected void paintComponent(Graphics g) {			
			super.paintComponent(g);
			g.drawImage(image, 0, 0, null);
		}
	}
	
	/**
	 * MenuBar support background
	 * @author Saigon Institute of Technology
	 *
	 */
	private class MyMenuBar extends JMenuBar {
		private Image image;
		
		public MyMenuBar(String bgImg) {
			this.image = GGBitmap.getImage(bgImg);
			setOpaque(Boolean.FALSE);
		}
		
		@Override
		protected void paintComponent(Graphics g) {			
			super.paintComponent(g);
//			g.drawImage(image, 0, 0, null);
		}
		
	}
	
	/**
	 * ToggleButton support background
	 * @author Saigon Institute of Technology
	 *
	 */
	private class MyToggleButton extends JToggleButton {		
		private Image image;
		
		public MyToggleButton(String bgImg) {
			this.image = GGBitmap.getImage(bgImg);			
		}
		
		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
		}
	}
		
	
	/**
	 * Button support background
	 * @author Saigon Institute of Technology
	 *
	 */
	private class MyButton extends JButton {		
		private Image image;
		
		public MyButton(String bgImg) {
			this.image = GGBitmap.getImage(bgImg);			
		}	
		
		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
		}
	}	
	
	/****************************** (src: terai.xrea.jp) Thanks to Terai about his wonderful job ******************/
	
	private static class TranslucentPopupMenu extends JPopupMenu {
		
		private static final Color ALPHA_ZERO = new Color(0, true);
		private static final Color POPUP_BACK = new Color(0, 0, 0, 80);

		@Override
		public boolean isOpaque() {
			return false;
		}

		@Override
		public Component add(Component c) {
			if (c instanceof JComponent) ((JComponent) c).setOpaque(false);			
			return c;
		}

		@Override
		public JMenuItem add(JMenuItem menuItem) {
			menuItem.setOpaque(false);
			return super.add(menuItem);
		}

		@Override
		public void show(Component c, int x, int y) {
			Window p = SwingUtilities.getWindowAncestor(TranslucentPopupMenu.this);
			if (p != null && p instanceof JWindow) ((JWindow) p).setBackground(ALPHA_ZERO);		    
			super.show(c, x, y);
		}

		@Override
		protected void paintComponent(Graphics g) {
			Graphics2D g2 = (Graphics2D) g.create();			
			g2.setPaint(POPUP_BACK);
			g2.fillRect(0, 0, getWidth(), getHeight());			
			g2.dispose();
		}
	}

	private static class TransparentMenu extends JMenu {
		public TransparentMenu(String title) {
			super(title);
		}

		// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4688783
		private JPopupMenu popupMenu;

		private void ensurePopupMenuCreated() {
			if (popupMenu == null) {
				this.popupMenu = new TranslucentPopupMenu();
				popupMenu.setOpaque(false);
				popupMenu.setInvoker(this);
				popupListener = createWinListener(popupMenu);
			}
		}

		@Override
		public JPopupMenu getPopupMenu() {
			ensurePopupMenuCreated();
			return popupMenu;
		}

		@Override
		public JMenuItem add(JMenuItem menuItem) {
			ensurePopupMenuCreated();
			menuItem.setOpaque(false);
			return popupMenu.add(menuItem);
		}

		@Override
		public Component add(Component c) {
			ensurePopupMenuCreated();
			if (c instanceof JComponent) ((JComponent) c).setOpaque(false);			
			popupMenu.add(c);
			return c;
		}

		@Override
		public void addSeparator() {
			ensurePopupMenuCreated();
			popupMenu.addSeparator();
		}

		@Override
		public void insert(String s, int pos) {
			if (pos < 0) throw new IllegalArgumentException("index less than zero.");			
			ensurePopupMenuCreated();
			popupMenu.insert(new JMenuItem(s), pos);
		}

		@Override
		public JMenuItem insert(JMenuItem mi, int pos) {
			if (pos < 0) throw new IllegalArgumentException("index less than zero.");
			ensurePopupMenuCreated();
			popupMenu.insert(mi, pos);
			return mi;
		}

		@Override
		public void insertSeparator(int index) {
			if (index < 0) throw new IllegalArgumentException("index less than zero.");
			ensurePopupMenuCreated();
			popupMenu.insert(new JPopupMenu.Separator(), index);
		}

		@Override
		public boolean isPopupMenuVisible() {
			ensurePopupMenuCreated();
			return popupMenu.isVisible();
		}
		
		@Override
		public void removeAll() {
			ensurePopupMenuCreated();
			super.removeAll();
			popupMenu.removeAll();
		}
	}
	
	/**
	 * This class implements thread to setup GameGrid lastly
	 * @author Saigon Institute of Technology
	 *
	 */
	private class SetupTableSafety implements Runnable {
		
		private SaigonTechNxtStudio studio;
		
		private SetupTableSafety(SaigonTechNxtStudio studio) {
			this.studio = studio;
		}

		@Override
		public void run() {
			//Simulation board
			table = new MyGameGrid(SaigonTechNxtContext.getNbhorzcells(), SaigonTechNxtContext.getNbvertcells(), 
												  SaigonTechNxtContext.getCellsize(), SaigonTechNxtContext.getGridcolor(), 
												  SaigonTechNxtContext.getImageName(), SaigonTechNxtContext.getIsNavigationBar(), 
												  SaigonTechNxtContext.getNbrotatablesprites(), true);
			
			//Layer designer
			panel = table.getPanel(0, SaigonTechNxtContext.getNbhorzcells(), 0, SaigonTechNxtContext.getNbvertcells());
			
			//Layout the GUI
			initComponents();
			
			table.setBgColor(Color.white);
			table.addMouseListener(studio, GGMouse.lDrag | GGMouse.move | GGMouse.lDClick | GGMouse.leave);	
		}		
	}
	
	private JPanel horizontalRuler = new MyRuler(MyRuler.HORIZONTAL);
	private JPanel verticalRuler = new MyRuler(MyRuler.VERTICAL);
	
	
	private final double STANDARD_GEAR = 12.5; //12.5cm
	private final int GEAR_LENGTH = 21; //dots per 12.5cm
    private final int THRESHOLD = 25; //cm per step
    
    //Formatter
    DecimalFormat decimalFormat = new DecimalFormat("#.#");

	/**
	 * Draw ruler on a component (graphics)
	 * @author Saigon Institute of Technology
	 *
	 */
	private class MyRuler extends JPanel {
		
	    private static final int HORIZONTAL = 0;
	    private static final int VERTICAL = 1;
	    private static final int SIZE = 35; //Ruler width

	    private int orientation; //Ruler orientation
	    private int increment; //Ruler step
	    private int units; //dots per unit (25 cm)

	    private MyRuler(int orientation) {
	        this.orientation = orientation;	        
	        setIncrementAndUnits();
	        setOpaque(Boolean.FALSE);
	    }
	    
	    private void setIncrementAndUnits() {
	    	units = (int) (((double) (GEAR_LENGTH * THRESHOLD)) / STANDARD_GEAR); // dots per cm
	    	increment = units;	       
	    }
	    
	    
	    @Override
	    protected void paintComponent(Graphics g) {	    	
	    	super.paintComponent(g);
	    	paintRuler(g);	    	
	    }
	    
	    /**
	     * Draw ruler
	     * @param g graphics
	     */
	    private void paintRuler(final Graphics g) {
	    	Color TICK_COLOR = Color.black;
	        Rectangle displayArea = g.getClipBounds();

	        //Display area background
//	        g.setColor(new Color(230, 163, 4));//Orange
//	        g.fillRect(displayArea.x, displayArea.y, displayArea.width, displayArea.height);

	        //Ruler label
	        g.setFont(new Font(Font.SERIF, Font.PLAIN, 15));
	        g.setColor(MENU_SELECTION_BACKGROUND);

	        //Holders
	        int end = 0;
	        int start = 0;
	        int tickLength = 0;
	        String text = null;

	        // Use display area to calculate first & last tick locations.
	        if (orientation == HORIZONTAL) {
	            start = (displayArea.x / increment) * increment;
	            end = (((displayArea.x + displayArea.width) / increment) + 1) * increment;
	        } else {
	            start = (displayArea.y / increment) * increment;
	            end = (((displayArea.y + displayArea.height) / increment) + 1) * increment;
	        }

	        //First tick: label & unit	        
	        if (start == 0) {
	            text = "cm";
	            tickLength = 10;
	            if (orientation == HORIZONTAL) {
	            	g.setColor(TICK_COLOR);
	            	g.drawLine(0, SIZE-1, 0, SIZE-tickLength-1);
	                g.setColor(MENU_SELECTION_BACKGROUND);
	                g.drawString(text, 0, 21);
	            } else {
	            	g.setColor(TICK_COLOR);
	                g.drawLine(SIZE-1, 0, SIZE-tickLength-1, 0);
	                g.setColor(MENU_SELECTION_BACKGROUND);
	                g.drawString(text, 0, 10);
	            }
	            text = null;
	            start = increment;
	        }

	        // ticks and labels
	        for (int i = start; i < end; i += increment) {
	            if (i % units == 0)  {
	                tickLength = 10;
	                text = Integer.toString(THRESHOLD * i/units);
	            } else {
	                tickLength = 7;
	                text = null;
	            }

	            if (tickLength != 0) {
	                if (orientation == HORIZONTAL) {
	                	g.setColor(TICK_COLOR);
	                    g.drawLine(i, SIZE-1, i, SIZE-tickLength-1);
	                    g.setColor(MENU_SELECTION_BACKGROUND);
	                    if (text != null) g.drawString(text, i-3, 21);
	                } else {
	                	g.setColor(TICK_COLOR);
	                    g.drawLine(SIZE-1, i, SIZE-tickLength-1, i);
	                    g.setColor(MENU_SELECTION_BACKGROUND);
	                    if (text != null) g.drawString(text, 0, i+3);
	                }
	            }
	        }
	    }
	}


	@Override
	public void windowActivated(WindowEvent arg0) {}

	@Override
	public void windowClosed(WindowEvent e) {
		//Same EXIT_ON_CLOSE
		System.exit(0);
	}

	@Override
	public void windowClosing(WindowEvent e) {
		//Call model to close server
		//TODO BinhNguyen
	}

	@Override
	public void windowDeactivated(WindowEvent e) {}

	@Override
	public void windowDeiconified(WindowEvent e) {}

	@Override
	public void windowIconified(WindowEvent e) {}

	@Override
	public void windowOpened(WindowEvent e) {}
}
